import json
import logging
import re
import uuid

from flask import g, request
from flask_appbuilder.models.sqla.interface import SQLAInterface
import modelarts
import sqlalchemy
from sqlalchemy import func

# Import base API functionalities
# 导入基础API功能
from myapp.apis.base import expose
# Import application instance, appbuilder, and database instance
# 导入应用实例、appbuilder和数据库实例
from myapp.app import app, appbuilder, db
# Import auto-learning configurations
# 导入自动学习配置
from myapp.configs.auto_learning import AUTO_LEARNING_CFGS
# Import common error codes
# 导入通用错误码
from myapp.const.error import CommonErrorCode
# Import business error and response wrapper
# 导入业务错误和响应包装器
from myapp.const.response import BizError, wrap_response
# Import service-related enums and configurations
# 导入与服务相关的枚举和配置
from myapp.const.service import (
    MODEL_DIMENSION_CFGS,
    EnumFramework,
    EnumModelDimension,
    EnumModelScene,
    EnumModelSource,
    EnumModelStatus,
    EnumModelType,
    EnumServingFramework,
    defaultStr,
)
# Import Workflow model
# 导入Workflow模型
from myapp.models.model_job import Workflow
# Import Labels model
# 导入Labels模型
from myapp.models.model_labels import Labels
# Import Model_Version model
# 导入Model_Version模型
from myapp.models.model_model_version import Model_Version
# Import Service model
# 导入Service模型
from myapp.models.model_serving import Service
# Import Training_Model model
# 导入Training_Model模型
from myapp.models.model_train_model import Training_Model
# Import Docker regular expression utilities
# 导入Docker正则表达式工具
from myapp.third.docker.regexp import has_tag, is_valid_image_name
# Import ModelArts client
# 导入ModelArts客户端
from myapp.third.modelarts import ma_client
# Import default ModelArts model IDs
# 导入默认ModelArts模型ID
from myapp.third.modelarts.configs import DEFAULT_MODEL_IDS
# Import ModelArts model information retrieval
# 导入ModelArts模型信息检索
from myapp.third.modelarts.model import get_model_info
# Import ModelArts API exception parser
# 导入ModelArts API异常解析器
from myapp.third.modelarts.tools import parse_apig_exception
# Import SWR validator
# 导入SWR验证器
from myapp.third.swr import validator
# Import environment utilities and string utilities
# 导入环境工具和字符串工具
from myapp.utils import env, strings
# Import environment check functions for ModelArts and private cloud
# 导入ModelArts和私有云的环境检查函数
from myapp.utils.env import is_modelarts, is_private
# Import storage utilities for path size and Alluxio storage check
# 导入存储工具，用于获取路径大小和检查Alluxio存储
from myapp.utils.storage import get_path_size, is_alluxio_storage, size_str
# Import ID generator for strings
# 导入字符串ID生成器
from myapp.utils.strings import id_generator
# Import base view classes for deletion mixin, custom filter, and model view
# 导入基础视图类，用于删除混合、自定义过滤器和模型视图
from myapp.views.base import DeleteMixin, MyappFilter, MyappModelView

# Import task category enum
# 导入任务类别枚举
from ..const.task_category import EnumTaskCategory
# Import AutoLearning model
# 导入AutoLearning模型
from ..models.model_auto_learning import AutoLearning
# Import MyUser model
# 导入MyUser模型
from ..models.model_user import MyUser
# Import base API class
# 导入基础API类
from .baseApi import MyappModelRestApi


# Get application configuration
# 获取应用配置
conf = app.config


# Define a custom filter for Training_Model
# 定义Training_Model的自定义过滤器
class Training_Model_Filter(MyappFilter):
    # Flag to indicate if public access is allowed
    # 标志，指示是否允许公共访问
    is_public_access = False

    # Apply the filter to the query
    # 将过滤器应用于查询
    # @pysnooper.snoop()
    def apply(self, query, func):
        # Log public access status
        # 记录公共访问状态
        logging.info('is_public_access')
        logging.info(self.is_public_access)
        # If public access is allowed, return the original query
        # 如果允许公共访问，则返回原始查询
        if self.is_public_access:
            return query
        # Get user roles and convert to lowercase
        # 获取用户角色并转换为小写
        user_roles = [role.name.lower() for role in list(self.get_user_roles())]
        # If user is an admin, return the original query
        # 如果用户是管理员，则返回原始查询
        if 'admin' in user_roles:
            return query
        # Otherwise, filter by the current user's ID
        # 否则，按当前用户ID过滤
        return query.filter(self.model.created_by_fk == g.user.id)


# Base class for Training_Model ModelView
# Training_Model ModelView的基类
class Training_Model_ModelView_Base:
    # Data model interface for Training_Model
    # Training_Model的数据模型接口
    datamodel = SQLAInterface(Training_Model)
    # Base permissions for the view
    # 视图的基本权限
    base_permissions = ['can_add', 'can_edit', 'can_delete', 'can_list', 'can_show']
    # Default order for listing items
    # 列表项的默认排序
    base_order = ('changed_on', 'desc')
    # Columns that can be used for ordering
    # 可用于排序的列
    order_columns = ['id']
    # Columns to display in the list view
    # 列表视图中显示的列
    list_columns = [
        'name',
        'framework',
        'modified',
        'type',
        'scene',
        'dimension',
        'changed_on',
        'describe',
        'is_public',
        'project_id',
        'sub_dimension',
        'source',
        'readme',
    ]
    # Columns to display in the show view
    # 详细视图中显示的列
    show_columns = [
        'name',
        'framework',
        'modified',
        'type',
        'scene',
        'dimension',
        'changed_on',
        'describe',
        'is_public',
        'project_id',
        'sub_dimension',
        'source',
        'readme',
    ]
    # Columns that can be used for searching
    # 可用于搜索的列
    search_columns = [
        'id',
        'created_by',
        'project',
        'name',
        'framework',
        'type',
        'scene',
        'dimension',
        'is_public',
        'project_id',
        'sub_dimension',
        'source',
        'type',
    ]

    # Columns to display in the add form
    # 添加表单中显示的列
    add_columns = [
        'project',
        'name',
        'version',
        'describe',
        'path',
        'framework',
        'run_id',
        'run_time',
        'metrics',
        'md5',
        'api_type',
        'pipeline_id',
        'is_public',
        'sub_dimension',
        'readme',
    ]
    # Columns to display in the edit form (empty means same as add_columns or dynamically determined)
    # 编辑表单中显示的列（空表示与add_columns相同或动态确定）
    edit_columns = []
    # Base filter for the view
    # 视图的基本过滤器
    base_filter = Training_Model_Filter

    # List of base filters to apply
    # 要应用的基本过滤器列表
    base_filters = [['id', base_filter, lambda: []]]

    # Pre-add hook: called before adding a new item
    # 预添加钩子：在添加新项目之前调用
    # @pysnooper.snoop(watch_explode=('item'))
    def pre_add(self, item):
        # If run_id is not provided, generate a random one
        # 如果未提供run_id，则生成一个随机的
        if not item.run_id:
            item.run_id = 'random_run_id_' + uuid.uuid4().hex[:32]

    # Pre-update hook: called before updating an existing item
    # 预更新钩子：在更新现有项目之前调用
    def pre_update(self, item):
        # If path is not provided, use the original path from src_item_json
        # 如果未提供path，则使用src_item_json中的原始路径
        if not item.path:
            item.path = self.src_item_json['path']
        # Call pre_add to handle run_id generation if needed
        # 调用pre_add来处理run_id生成（如果需要）
        self.pre_add(item)

    # Pre-list hook: called before listing items, used for adapting filter fields
    # 预列表钩子：在列出项目之前调用，用于适配过滤字段
    def pre_list(self, **_args):
        # Set public access flag to false by default
        # 默认将公共访问标志设置为false
        self.base_filter.is_public_access = False
        # If filters are present in arguments
        # 如果参数中存在过滤器
        if 'filters' in _args:
            filters = _args.get('filters')
            for f in filters:
                # Adapt dimension, scene, type, and source filter values
                # 适配维度、场景、类型和来源过滤值
                if f['col'] == 'dimension':
                    f['value'] = EnumModelDimension.get_val(f['value'])
                elif f['col'] == 'scene':
                    f['value'] = EnumModelScene.get_val(f['value'])
                elif f['col'] == 'type':
                    f['value'] = EnumModelType.get_val(f['value'])
                elif f['col'] == 'source':
                    f['value'] = EnumModelSource.get_val(f['value'])
                    logging.info(f['value'])
                    logging.info(EnumModelSource.internal.value)
                    # If source is internal, allow public access
                    # 如果来源是内部，则允许公共访问
                    if f['value'] is EnumModelSource.internal.value:
                        self.base_filter.is_public_access = True
                        logging.info(self.base_filter.is_public_access)
        logging.info(self.base_filter.is_public_access)
        return _args

    # Pre-get-list hook: called after fetching list data, used for adapting fields
    # 预获取列表钩子：在获取列表数据后调用，用于适配字段
    def pre_get_list(self, resp):
        for data in resp['data']:
            # Convert enum values to names for display
            # 将枚举值转换为名称以供显示
            data['type'] = EnumModelType.get_name(data['type'])
            data['scene'] = EnumModelScene.get_name(data['scene'])
            data['dimension'] = EnumModelDimension.get_name(data['dimension'])
            data['source'] = EnumModelSource.get_name(data['source'])

            # Get the count of model versions for each model
            # 获取每个模型的模型版本数量
            cnt = db.session.query(Model_Version).filter_by(model_id=data['id']).count()
            data['versions'] = cnt
        return resp

    # Pre-get hook: called after fetching single item data, used for adapting fields
    # 预获取钩子：在获取单个项目数据后调用，用于适配字段
    def pre_get(self, resp):
        data = resp['data']

        # Convert enum values to names for display
        # 将枚举值转换为名称以供显示
        resp['data']['type'] = EnumModelType.get_name(data['type'])
        resp['data']['scene'] = EnumModelScene.get_name(data['scene'])
        resp['data']['dimension'] = EnumModelDimension.get_name(data['dimension'])
        resp['data']['source'] = EnumModelSource.get_name(data['source'])

        # Get the count of model versions for the model
        # 获取模型的模型版本数量
        cnt = db.session.query(Model_Version).filter_by(model_id=data['id']).count()
        data['versions'] = cnt
        return resp


# Training_Model ModelView class, inheriting from base and including DeleteMixin
# Training_Model ModelView类，继承自基类并包含DeleteMixin
class Training_Model_ModelView(Training_Model_ModelView_Base, MyappModelView, DeleteMixin):
    # Data model interface for Training_Model
    # Training_Model的数据模型接口
    datamodel = SQLAInterface(Training_Model)


# Add the Training_Model_ModelView to the appbuilder
# 将Training_Model_ModelView添加到appbuilder
appbuilder.add_view(
    Training_Model_ModelView,
    '模型管理',
    icon='fa-hdd-o',
    category='服务化',
    category_icon='fa-tasks',
)


# Training_Model ModelView API class, inheriting from base and MyappModelRestApi
# Training_Model ModelView API类，继承自基类和MyappModelRestApi
class Training_Model_ModelView_Api(Training_Model_ModelView_Base, MyappModelRestApi):  # noqa
    # Data model interface for Training_Model
    # Training_Model的数据模型接口
    datamodel = SQLAInterface(Training_Model)
    # Base order for listing items (commented out, using base_order from base class)
    # 列表项的默认排序（已注释，使用基类中的base_order）
    # base_order = ('id', 'desc')
    # Route base for the API endpoint
    # API端点的路由基础
    route_base = '/training_model_modelview/api'
    # Base permissions for the API
    # API的基本权限
    base_permissions = MyappModelRestApi.base_permissions
    # Append additional permissions
    # 追加额外权限
    base_permissions.append('can_import_model')
    base_permissions.append('can_add_model')

    # List API endpoint
    # 列表API端点
    @expose('/list', methods=['POST'])
    @wrap_response
    def list(self):
        # Get request JSON data
        # 获取请求JSON数据
        req = request.json
        # Get pagination parameters
        # 获取分页参数
        page = req.get('page', 0)
        page_size = req.get('page_size', 10)
        # Get filter parameters
        # 获取过滤参数
        filters = req.get('filters', [])

        # Get region key from global context
        # 从全局上下文获取区域键
        region_key = g.region.key

        # Validate page and page_size parameters
        # 验证page和page_size参数
        if not isinstance(page, int):
            raise BizError(1000, "page参数类型错误，仅能输入数字")
        if not isinstance(page_size, int):
            raise BizError(1000, "page_size参数类型错误，仅能输入数字")
        if page_size > 999:
            raise BizError(1000, "page_size最大值不得超过999")

        # Process filters
        # 处理过滤器
        for f in filters:
            # Check if the column exists in Training_Model
            # 检查列是否存在于Training_Model中
            if not hasattr(Training_Model, f['col']):
                raise BizError(1000, "未找到相关数据")
            # Convert enum values for specific columns
            # 转换特定列的枚举值
            if f['col'] == 'dimension':
                f['value'] = EnumModelDimension.get_val(f['value'])
            elif f['col'] == 'scene':
                f['value'] = EnumModelScene.get_val(f['value'])
            elif f['col'] == 'type':
                f['value'] = EnumModelType.get_val(f['value'])
            elif f['col'] == 'source':
                if f['opr'] == 'in':
                    s_list = f['value']
                    t_list = []
                    for s in s_list:
                        t_list.append(EnumModelSource.get_val(s))
                    f['value'] = t_list
                else:
                    f['value'] = EnumModelSource.get_val(f['value'])

        # Sanitize page and page_size
        # 清理page和page_size
        if page < 0:
            page = 0
        if page_size < 0:
            page_size = 0
        if page_size > 999:
            page_size = 999

        # Build the base query
        # 构建基础查询
        q = db.session.query(Training_Model)

        # Apply user-specific filters based on roles
        # 根据角色应用用户特定过滤器
        if g.user.is_admin:
            pass
        elif g.user.is_tenant_admin:
            q = q.outerjoin(MyUser, MyUser.id == Training_Model.created_by_fk).filter(
                MyUser.tenant_id == g.user.tenant_id
            )
        else:
            q = q.filter(Training_Model.created_by_fk == g.user.id)

        # Apply additional filters
        # 应用额外过滤器
        for f in filters:
            if f['opr'] == 'in' and f['col'] == 'labels' and len(f['value']) > 0:
                q = (
                    q.join(Labels, Labels.rel_id == Training_Model.id)
                    .filter(Labels.rel_type == 'model')
                    .filter(Labels.name.in_(f['value']))
                )
            elif f['opr'] == 'eq':
                q = q.filter(getattr(Training_Model, f['col']) == f['value'])
            elif f['opr'] == 'ct':
                q = q.filter(getattr(Training_Model, f['col']).like(f'%{f["value"]}%'))
            elif f['opr'] == 'in':
                q = q.filter(getattr(Training_Model, f['col']).in_(f['value']))
            else:
                raise BizError(1000, f"不支持的filter,opr:{f['opr']}")

        # Apply region filter if available
        # 如果可用，则应用区域过滤器
        if region_key is not None:
            q = q.filter(Training_Model.region == region_key)

        # Get total count and paginated results
        # 获取总数和分页结果
        count = q.count()
        results = (
            q.order_by(Training_Model.created_on.desc())
            .offset(page * page_size)
            .limit(page_size)
            .all()
        )
        if results is None:
            return {'count': 0, 'data': []}

        # Prepare response data
        # 准备响应数据
        ret = []
        model_ids = [item.id for item in results]
        id2labels = {}
        model_ids = list(set(model_ids))
        if len(model_ids) > 0:
            labels = (
                db.session.query(Labels.name, Labels.rel_id)
                .filter(Labels.rel_type == 'model')
                .filter(Labels.rel_id.in_(model_ids))
                .all()
            )
            for item in labels:
                lst_labels = id2labels.get(item[1], [])
                lst_labels.append(item[0])
                id2labels[item[1]] = lst_labels

        model_versions_cnt = (
            db.session.query(Model_Version.model_id, func.count(Model_Version.id))
            .group_by(Model_Version.model_id)
            .filter(Model_Version.model_id.in_(model_ids))
            .all()
        )
        id2versions = {}
        for item in model_versions_cnt:
            id2versions[item[0]] = item[1]

        for item in results:
            ret.append(
                {
                    'id': item.id,
                    'name': item.name,
                    'describe': item.describe,
                    'versions': id2versions.get(item.id, 0),
                    'type': EnumModelType.get_name(item.type),
                    'scene': EnumModelScene.get_name(item.scene),
                    'dimension': EnumModelDimension.get_name(item.dimension),
                    'source': EnumModelSource.get_name(item.source),
                    'labels': id2labels.get(item.id, []),
                    'framework': item.framework,
                    'sub_dimension': item.sub_dimension,
                    'is_public': item.is_public,
                    'created_on': item.created_on.strftime('%Y-%m-%d %H:%M:%S'),
                    'changed_on': item.changed_on.strftime('%Y-%m-%d %H:%M:%S'),
                    'train_config': json.loads(item.train_config),
                    'region': item.region,
                }
            )

        return {'count': count, 'data': ret}

    # Delete model API endpoint
    # 删除模型API端点
    @expose('/delete/<model_id>', methods=['GET'])
    @wrap_response
    def del_model(self, model_id):
        # Check if the model is being used by any inference service
        # 检查模型是否被任何推理服务使用
        srv = db.session.query(Service.name, Service.id).filter(Service.model_id == model_id).all()
        if srv and len(srv) > 0:
            name = ''
            for s in srv:
                name += f' {s.name}'
            raise BizError(1000, f'无法删除该模型，模型正在被推理服务使用,请先删除服务：{name}')

        # Check if there are any model versions associated with this model
        # 检查此模型是否关联任何模型版本
        query = db.session.query(Model_Version).filter(Model_Version.model_id == model_id)

        # Apply user-specific filters
        # 应用用户特定过滤器
        if g.user.is_admin:
            pass
        elif g.user.is_tenant_admin:
            query = query.outerjoin(MyUser, MyUser.id == Model_Version.created_by_fk).filter(
                MyUser.tenant_id == g.user.tenant_id
            )
        else:
            query = query.filter(Model_Version.created_by_fk == g.user.id)

        model_version = query.all()
        if len(model_version) > 0:
            raise BizError(1000, '无法删除该模型，模型版本不为空,请先删除模型版本')

        # Clean up resources on ModelArts if applicable
        # 如果适用，清理ModelArts上的资源
        if is_modelarts():
            mvs = query.all()
            if mvs:
                for mv in mvs:
                    if mv.source == EnumModelSource.auto_learning.value:
                        logging.info(f'自动学习模型不能删除，跳过 {mv.ma_id}')
                        continue
                    if mv.ma_id and len(mv.ma_id) > 0:
                        try:
                            logging.info(f'删除modelarts模型 {mv.ma_id}')
                            ma_client.delete_model(mv.ma_id)
                        except modelarts.exception.apig_exception.APIGException as e:
                            err_code, _ = parse_apig_exception(e)
                            if err_code != 'ModelArts.3001':  # Model does not exist / 模型不存在
                                raise e

        # Delete the Training_Model record
        # 删除Training_Model记录
        query = db.session.query(Training_Model).filter(Training_Model.id == model_id)
        if g.user.is_admin:
            pass
        elif g.user.is_tenant_admin:
            sub_query = db.session.query(MyUser.id).filter(MyUser.tenant_id == g.user.tenant_id)
            query = query.filter(Training_Model.created_by_fk.in_(sub_query.subquery()))
        else:
            query = query.filter(Training_Model.created_by_fk == g.user.id)
        query.delete(synchronize_session=False)

        # Delete associated labels
        # 删除关联标签
        db.session.query(Labels).filter(
            Labels.rel_type == 'model', Labels.rel_id == model_id
        ).delete()

        # Commit changes to the database
        # 提交更改到数据库
        db.session.commit()

    # Add model API endpoint
    # 添加模型API端点
    # @has_access_api
    @expose('/add', methods=['POST'])
    @wrap_response
    def add_model(self):
        # Get request JSON data
        # 获取请求JSON数据
        req = request.json

        # Validate model name
        # 验证模型名称
        name = req.get('name', None)
        if name is None:
            raise BizError(CommonErrorCode.INVALID_INPUT_PARAM, '模型名称必填')
        if not strings.is_valid_name(name):
            raise BizError(
                CommonErrorCode.INVALID_INPUT_PARAM,
                f'模型名称 {name} 不符合规范，请换个名称。名称'
                '支持1-48位可见字符（含中文），可以包含字母、中文、数宇、中划线、下划线。',
            )

        # Get region key
        # 获取区域键
        region_key = g.region.key

        # Initialize status and ModelArts ID
        # 初始化状态和ModelArts ID
        cur_status = EnumModelStatus.success.value
        ma_id = ''

        # Get framework and adapt dimension for fine-tuning tasks
        # 获取框架并适配微调任务的维度
        framework = defaultStr if req.get('framework') is None else req.get('framework')
        dimension = req.get('dimension')
        if dimension == 'llm':
            dimension = 'nlp'
        if dimension == 'vqa':
            dimension = 'omni'
        # Construct Training_Model object
        # 构造Training_Model对象
        model = Training_Model()
        model.project_id = req.get('project')
        model.name = req['name']
        model.describe = req.get('describe', '')
        model.framework = framework
        model.sub_dimension = req.get('sub_dimension')
        model.type = EnumModelType.get_val(req.get('type'))
        model.scene = EnumModelScene.get_val(req.get('scene'))
        model.dimension = EnumModelDimension.get_val(dimension)
        model.readme = req.get('readme', '')
        model.source = EnumModelSource.get_val(req.get('source'))
        model.train_config = json.dumps(req.get('train_config', {}), ensure_ascii=False)
        model.region = region_key

        # Add model to session and flush to get ID
        # 将模型添加到会话并刷新以获取ID
        db.session.add(model)
        db.session.flush()

        # Save labels
        # 保存标签
        labels = req.get('labels', [])
        labels = list({label.strip() for label in labels})
        if len(labels) > 0:
            for name in labels:
                label = Labels()
                label.name = name
                label.rel_type = 'model'
                label.rel_id = model.id
                db.session.add(label)

                # Create model version data
                # 创建模型版本数据
        info = req.get('version_info', {})
        # If it's a large model fine-tuning task, get image and APIs from the task
        # 如果是大模型微调任务，从任务中获取镜像和API
        source = EnumModelSource.get_val(req.get('source'))
        source_id = req.get('source_id')
        if source == EnumModelSource.finetune.value:
            if not source_id:
                raise BizError(CommonErrorCode.INVALID_INPUT_PARAM, '训练任务ID必填')
            task = db.session.query(AutoLearning).filter_by(training_job_id=source_id).first()
            if not task:
                raise BizError(CommonErrorCode.INVALID_INPUT_PARAM, '任务不存在')

            job = db.session.query(Workflow).filter_by(id=source_id).first()
            if job is None:
                raise BizError(CommonErrorCode.NOT_FOUND, '训练任务ID不存在')
            resource_type = json.loads(job.algorithm).get('resource', {}).get('resource_type')
            lora = json.loads(job.algorithm).get('resource', {}).get('lora')
            info['image'] = get_image_by_type(task.task_type, resource_type, lora)
            info['apis'] = get_apis_by_type(task.task_type)
        if info is not None:
            serving_framework = info.get('serving_framework', defaultStr)
            version_name = info.get('name', '0.0.0')
            # Validate version name format
            # 验证版本名称格式
            if not re.search('^\d\.\d\.\d$', version_name):
                raise BizError(1000, f'版本号不合法,{version_name},请按照 0.0.1 格式填写')

            image = info.get('image')
            if image is not None and len(image) > 0:
                image = image.strip()
                # Validate image name
                # 验证镜像名称
                if not is_valid_image_name(image):
                    raise BizError(CommonErrorCode.INVALID_INPUT_PARAM, f'无效的镜像地址,{image}')
                # Add ':latest' tag if not present
                # 如果不存在，则添加':latest'标签
                if not has_tag(image):
                    image = image + ':latest'

            # ModelArts specific logic for creating models
            # ModelArts创建模型的特定逻辑
            if is_modelarts():
                if serving_framework == EnumServingFramework.other.value:
                    name = req.get('name')
                    dependencies = info.get('dependencies')
                    if image is not None and len(image) > 0:
                        # Check if image is a valid ModelArts image
                        # 检查镜像是否为有效的ModelArts镜像
                        if not validator.is_valid_image(image):
                            raise BizError(
                                CommonErrorCode.INVALID_INPUT_PARAM, '请使用swr仓库的镜像'
                            )

                        ma_model_name = f'taichu-{name}'
                        if not env.is_online():
                            ma_model_name = f'taichu-{env.get_env()}-{name}'
                        # Generate random string for ModelArts model name
                        # 为ModelArts模型名称生成随机字符串
                        ma_model_name = f'{ma_model_name}-{id_generator()}'
                        ma_id = ma_client.create_model(
                            name=ma_model_name,  # Model name / 模型名称
                            desc=f'由taichu平台创建,模型名称:{name},创建人:{g.user.id}',  # Description / 描述
                            model_version=version_name,  # Model version / 模型版本
                            source_location=image,  # Model file path / 模型文件路径
                            model_type='Image',  # Model type (currently only image) / 模型类型（目前只开放image）
                            install_type=['real-time'],  # Deployment type (default real-time service) / 部署类型（默认为在线服务）
                            dependencies=dependencies,  # pip dependencies / pip依赖
                        )
                        cur_status = EnumModelStatus.importing.value

                elif serving_framework == EnumServingFramework.torchserve.value:
                    ma_id = DEFAULT_MODEL_IDS[0]  # Temporarily fixed ID for single version / 暂时只有单版本的固定id

            source = EnumModelSource.get_val(info.get('source'))
            source_id = info.get('source_id')
            version_model = Model_Version()
            version_model.model_id = model.id
            version_model.project_id = req.get('project')
            version_model.name = version_name
            version_model.tenant_id = g.user.tenant_id
            version_model.region = g.region.key
            infer_code_path = info.get('infer_code_path')
            model_path = None
            resource_type = None
            deploy_cfgs = None
            lora_adapter = 0
            # Logic for fine-tuning source
            # 微调来源的逻辑
            if source == EnumModelSource.finetune.value:
                # If fine-tuning task, query model path from workflow
                # 若微调任务，查表读取模型地址
                if not source_id:
                    raise BizError(CommonErrorCode.INVALID_INPUT_PARAM, '训练任务ID必填')
                job = db.session.query(Workflow).filter_by(id=source_id).first()
                if job is None:
                    raise BizError(CommonErrorCode.NOT_FOUND, '训练任务ID不存在')
                model_path = job.get_output_path()

                workflow = db.session.query(Workflow).filter_by(id=source_id).first()
                if not workflow:
                    raise BizError(CommonErrorCode.NOT_FOUND, '训练任务ID不存在')

                algorithm = json.loads(workflow.algorithm)
                task_type = json.loads(workflow.labels).get('task_type')
                if task_type is None:
                    raise BizError(
                        CommonErrorCode.NOT_FOUND, f'训练任务 {source_id} 没有task_type'
                    )
                resource = algorithm.get('resource', {})
                resource_type = algorithm.get('resource', {}).get('resource_type', '')
                pool_type = resource.get('pool_type')
                deploy_cfgs = {'pool_type': pool_type, 'task_type': task_type}
                lora = resource.get('lora')
                all_cfgs = AUTO_LEARNING_CFGS.get(task_type)
                envs = all_cfgs.get(resource_type, {}).get('envs')
                # specifications = all_cfgs.get(resource_type, {}).get('specifications')
                specifications = (
                    all_cfgs.get(resource_type, {})
                    .get('resources', {})
                    .get(workflow.region, {})
                    .get('specifications')
                )
                if lora:
                    envs = all_cfgs.get(resource_type, {}).get('lora', {}).get('envs')
                    specifications = (
                        # all_cfgs.get(resource_type, {}).get('lora', {}).get('specifications')
                        all_cfgs.get(resource_type, {})
                        .get('lora', {})
                        .get('resources', {})
                        .get(workflow.region, {})
                        .get('specifications')
                    )
                deploy_cfgs['envs'] = envs
                deploy_cfgs['specifications'] = specifications
                if algorithm.get('lora_adapter', False) and dimension == 'nlp':
                    lora_adapter = 1

            # Logic for upload source
            # 上传来源的逻辑
            if source == EnumModelSource.upload.value:
                if infer_code_path is None or len(infer_code_path) == 0:
                    raise BizError(
                        CommonErrorCode.INVALID_INPUT_PARAM, '本地导入模型时，模型包路径必填'
                    )
                if image is None or len(image) == 0:
                    raise BizError(
                        CommonErrorCode.INVALID_INPUT_PARAM, '本地导入模型时，镜像地址必填'
                    )
            # Calculate model size for private cloud
            # 计算私有云的模型大小
            if is_private():
                model_size = get_path_size(model_path, g.region.key)
                code_size = get_path_size(infer_code_path, g.region.key)
                version_model.size = size_str(model_size + code_size)

            version_model.path = json.dumps(
                {'model_path': model_path, 'infer_code_path': infer_code_path}
            )
            version_model.image_id = info.get('image_id')
            version_model.image = image
            version_model.volume_mount = info.get('volume_mount')
            version_model.source = source
            version_model.source_id = source_id
            version_model.describe = info.get('describe')
            version_model.cpu_type = info.get('cpu_type')
            version_model.port = info.get('port')
            version_model.apis = info.get('apis')
            version_model.command = info.get('command')
            version_model.working_dir = info.get('working_dir')
            version_model.status = cur_status
            version_model.ma_id = ma_id
            version_model.serving_framework = serving_framework
            if resource_type:
                version_model.device_type = resource_type
            if deploy_cfgs:
                version_model.deploy_cfgs = json.dumps(deploy_cfgs, ensure_ascii=False)
            version_model.lora_adapter = lora_adapter
            db.session.add(version_model)
            db.session.commit()

            return {'id': model.id, 'version_info': {'id': version_model.id}}
        else:
            logging.info('info is none')
            db.session.commit()
            return {'id': model.id}

    # Edit model API endpoint
    # 编辑模型API端点
    @expose('/edit/<model_id>', methods=['POST'])
    @wrap_response
    def edit(self, model_id):
        # Get request JSON data
        # 获取请求JSON数据
        req = request.json
        name = req.get('name')

        # Query the Training_Model by ID
        # 按ID查询Training_Model
        query = db.session.query(Training_Model).filter_by(id=model_id)

        # Apply user-specific filters
        # 应用用户特定过滤器
        if g.user.is_admin:
            pass
        elif g.user.is_tenant_admin:
            query = query.outerjoin(MyUser, MyUser.id == Training_Model.created_by_fk).filter(
                MyUser.tenant_id == g.user.tenant_id
            )
        else:
            query = query.filter(Training_Model.created_by_fk == g.user.id)

        model = query.first()
        if not model:
            raise BizError(CommonErrorCode.NOT_FOUND, '模型不存在')
        # Check user permissions
        # 检查用户权限
        if not g.user.is_admin and model.created_by_fk != g.user.id:
            raise BizError(1000, '您无对该资源操作的权限')
        # Update model name if provided
        # 如果提供，则更新模型名称
        if name:
            model.name = name
        # Commit changes to the database
        # 提交更改到数据库
        db.session.commit()

    # Get dimension configurations API endpoint
    # 获取维度配置API端点
    @expose('/configs/dimension', methods=['GET'])
    @wrap_response
    def dimension_configs(self):
        # Get dimension configurations from MODEL_DIMENSION_CFGS
        # 从MODEL_DIMENSION_CFGS获取维度配置
        dimension = MODEL_DIMENSION_CFGS.get('dimension')

        result = []
        for item in dimension:
            subclass = item.get('subclass')
            if subclass:
                for item1 in subclass:
                    result.append(
                        {
                            'dimension_type': item.get('class'),
                            'dimension_name': item.get('cn_name'),
                            'model_type': item1.get('class'),
                            'model_type_name': item1.get('cn_name'),
                        }
                    )

            else:
                result.append(
                    {
                        'dimension_type': item.get('class'),
                        'dimension_name': item.get('cn_name'),
                        'model_type': None,
                        'model_type_name': None,
                    }
                )

        result.append(
            {
                'dimension_type': None,
                'dimension_name': None,
                'model_type': 'other',
                'model_type_name': '其他',
            }
        )
        return result

    # Get framework configurations API endpoint
    # 获取框架配置API端点
    @expose('/configs/framework', methods=['GET'])
    @wrap_response
    def framework_configs(self):
        # Get framework configurations from MODEL_DIMENSION_CFGS
        # 从MODEL_DIMENSION_CFGS获取框架配置
        framework = MODEL_DIMENSION_CFGS.get('framework')

        result = []
        for item in framework:
            result.append(
                {
                    'framework_type': item.get('class'),
                    'framework_name': item.get('cn_name'),
                }
            )

        return result

    # Static method to create a model (used in auto-learning)
    # 创建模型的静态方法（用于自动学习）
    @staticmethod
    def create_model(
        name=None,  # 模型名称 / Model name
        project_id=None,  # 项目id / Project ID
        path=None,  # 模型地址 / Model path
        size='0',  # 模型大小 / Model size
        desc=None,  # 模型描述 / Model description
        source_job_id=None,  # 来源的训练任务id / Source training job ID
        task_type=None,  # 下游任务类型 image_caption vqa retrieval / Downstream task type (e.g., image_caption, vqa, retrieval)
        framework=EnumFramework.other.name,  # 算法框架 mindspore pytorch / Algorithm framework (e.g., mindspore, pytorch)
        serving_framework=EnumServingFramework.other.name,  # serving框架 mindspore-serving / Serving framework (e.g., mindspore-serving)
        version=None,  # 模型版本 / Model version
        kind=None,
        resource_type=None,
        lora=None,
        deploy_cfgs=None,  # 模型部署相关的配置 / Model deployment related configurations
        dbsession=db.session,
        region='default',
        **kwargs,
    ):
        # Initialize deploy_cfgs if None
        # 如果deploy_cfgs为None，则初始化
        if deploy_cfgs is None:
            deploy_cfgs = {}
        # Validate required parameters
        # 验证必需参数
        if name is None:
            raise BizError(1000, '模型名称必填')
        if path is None:
            raise BizError(1000, '模型地址必填')
        if project_id is None:
            raise BizError(1000, '项目ID必填')
        if version is None:
            version = '0.0.1'
        if source_job_id is None:
            source_job_id = ''

        # Create Training_Model object
        # 创建Training_Model对象
        model = Training_Model()
        model.project_id = project_id
        model.name = name
        model.describe = desc
        model.framework = framework
        model.region = region
        # Query workflow to get algorithm and pretrain model information
        # 查询工作流以获取算法和预训练模型信息
        workflow = dbsession.query(Workflow).filter_by(id=source_job_id).first()
        if not workflow:
            logging.error(f'workflow:{source_job_id}不存在')
            return
        pretrain_model = json.loads(workflow.algorithm).get('pretrain_model', {})
        dimension = pretrain_model.get('dimension', '')
        if dimension:
            if dimension == 'llm':
                model.dimension = EnumModelDimension.nlp.value
                model.type = EnumModelType.text_generation.value
            elif dimension == 'vqa':
                model.dimension = EnumModelDimension.omni.value
                model.type = EnumModelType.visual_question_answer.value
        task_category = json.loads(workflow.algorithm).get('resource', {}).get('task_category')
        model.train_config = json.dumps(
            pretrain_model, ensure_ascii=False
        )  # Store parameters in model table / 将参数也写在model表里，后面要用
        # Set model source based on task category
        # 根据任务类别设置模型来源
        if task_category == EnumTaskCategory.auto_learning.value:  # Auto-learning (will be deprecated) / 自动学习（后面会废弃）
            model.source = EnumModelSource.auto_learning.value
        elif task_category == EnumTaskCategory.finetune.value:
            model.source = EnumModelSource.finetune.value
        elif task_category == EnumTaskCategory.post_pretraining.value:
            model.source = EnumModelSource.post_pretraining.value
        elif task_category == EnumTaskCategory.rlhf.value:
            model.source = EnumModelSource.rlhf.value

        # Set creator ID if provided
        # 如果提供，则设置创建者ID
        if 'creator_id' in kwargs:
            model.created_by_fk = kwargs['creator_id']

        # Add model to session and flush
        # 将模型添加到会话并刷新
        dbsession.add(model)
        dbsession.flush()

        # Create Model_Version object
        # 创建Model_Version对象
        version_model = Model_Version()
        version_model.model_id = model.id
        version_model.project_id = project_id
        version_model.name = version
        version_model.path = json.dumps({'model_path': path})
        version_model.image = get_image_by_type(task_type, resource_type, lora)
        version_model.license_id = get_license_id_by_type(task_type, resource_type, lora)
        version_model.port = '8080'
        version_model.source = model.source
        version_model.source_id = source_job_id
        version_model.serving_framework = serving_framework
        version_model.device_type = resource_type
        version_model.deploy_cfgs = json.dumps(deploy_cfgs, ensure_ascii=False)
        version_model.region = region
        # ModelArts specific logic for model version
        # ModelArts模型版本的特定逻辑
        if is_modelarts():
            ma_id = get_ma_id_by_type(task_type, resource_type, lora)
            version_model.ma_id = ma_id
            if len(version_model.image) == 0:
                info = get_model_info(ma_id)
                version_model.image = info.get('source_location', '')

        version_model.status = EnumModelStatus.success.value
        version_model.size = size
        if size == '0':
            version_model.status = EnumModelStatus.failed.value
        version_model.apis = get_apis_by_type(task_type)
        # Set creator ID for model version if provided
        # 如果提供，则设置模型版本的创建者ID
        if 'creator_id' in kwargs:
            version_model.created_by_fk = kwargs['creator_id']
        # Set lora_adapter for LLM dimension
        # 为LLM维度设置lora_adapter
        if dimension == 'llm':
            if json.loads(workflow.algorithm).get('lora_adapter', False):
                version_model.lora_adapter = 1
        # Add model version to session and commit
        # 将模型版本添加到会话并提交
        dbsession.add(version_model)
        dbsession.commit()

    # Static method to create a model version
    # 创建模型版本的静态方法
    @staticmethod
    def create_model_version(
        name=None,  # 模型名称 / Model name
        project_id=None,  # 项目id / Project ID
        path=None,  # 模型地址 / Model path
        size='0',  # 模型大小 / Model size
        desc=None,  # 模型描述 / Model description
        source_job_id=None,  # 来源的训练任务id / Source training job ID
        task_type=None,  # 下游任务类型 image_caption vqa retrieval / Downstream task type (e.g., image_caption, vqa, retrieval)
        framework=EnumFramework.other.name,  # 算法框架 mindspore pytorch / Algorithm framework (e.g., mindspore, pytorch)
        serving_framework=EnumServingFramework.other.name,  # serving框架 mindspore-serving / Serving framework (e.g., mindspore-serving)
        version=None,  # 模型版本 / Model version
        kind=None,
        resource_type=None,
        lora=None,
        deploy_cfgs=None,  # 模型部署相关的配置 / Model deployment related configurations
        dbsession=db.session,
        region='default',
        **kwargs,
    ):
        # Initialize deploy_cfgs if None
        # 如果deploy_cfgs为None，则初始化
        if deploy_cfgs is None:
            deploy_cfgs = {}
        # Validate required parameters
        # 验证必需参数
        if name is None:
            raise BizError(1000, '模型名称必填')
        if path is None:
            raise BizError(1000, '模型地址必填')
        if project_id is None:
            raise BizError(1000, '项目ID必填')
        if version is None:
            version = '0.0.1'
        if source_job_id is None:
            source_job_id = ''

        # Create model version data
        # 创建模型版本数据
        workflow = dbsession.query(Workflow).filter_by(id=source_job_id).first()
        model_id = (
            json.loads(workflow.algorithm).get('model_compress_info', {}).get('origin_model_id')
        )

        # Idempotency check to avoid duplicate creation
        # 幂等判断，避免重复创建
        model_version = (
            dbsession.query(Model_Version)
            .filter_by(model_id=model_id, source_id=source_job_id)
            .first()
        )
        if model_version:
            logging.info(f'模型版本model_id:{model_id}:source_id{source_job_id}已存在')
            return
        model = dbsession.query(Training_Model).filter(Training_Model.id == model_id).first()
        # Get the maximum model version
        # 获取最大的模型版本
        max_model_version = (
            dbsession.query(Model_Version)
            .filter_by(model_id=model_id)
            .order_by(Model_Version.id.desc())
            .first()
        )
        if max_model_version:
            version = increment_version(max_model_version.name)

        version_model = Model_Version()
        version_model.model_id = model.id
        version_model.project_id = project_id
        version_model.name = version
        version_model.path = json.dumps({'model_path': path})
        version_model.image = get_image_by_type(task_type, resource_type, lora)
        version_model.license_id = get_license_id_by_type(task_type, resource_type, lora)
        version_model.port = '8080'
        version_model.source = model.source
        version_model.source_id = source_job_id
        version_model.serving_framework = serving_framework
        version_model.device_type = resource_type
        version_model.deploy_cfgs = json.dumps(deploy_cfgs, ensure_ascii=False)
        version_model.compress = 1
        version_model.region = region

        # Public cloud: ModelArts
        # 公有云：modelarts
        if is_modelarts():
            ma_id = get_ma_id_by_type(task_type, resource_type, lora)
            version_model.ma_id = ma_id
            if len(version_model.image) == 0:
                info = get_model_info(ma_id)
                version_model.image = info.get('source_location', '')

        version_model.status = EnumModelStatus.success.value
        version_model.size = size
        if size == '0':
            version_model.status = EnumModelStatus.failed.value
        version_model.apis = get_apis_by_type(task_type)
        # Set creator ID for model version if provided
        # 如果提供，则设置模型版本的创建者ID
        if 'creator_id' in kwargs:
            version_model.created_by_fk = kwargs['creator_id']
        # Add model version to session and commit
        # 将模型版本添加到会话并提交
        dbsession.add(version_model)
        dbsession.commit()
        # Update workflow algorithm with model compression info
        # 使用模型压缩信息更新工作流算法
        algorithm = json.loads(workflow.algorithm)
        model_compress_info = algorithm.get('model_compress_info', {})
        model_compress_info['new_model_id'] = model_id
        model_compress_info['new_model_version_id'] = version_model.id
        model_compress_info['new_model_name'] = model.name
        model_compress_info['new_model_version_name'] = version_model.name
        algorithm['model_compress_info'] = model_compress_info
        workflow.algorithm = json.dumps(algorithm, indent=4, ensure_ascii=False)
        dbsession.commit()

    # List labels API endpoint
    # 列表标签API端点
    @expose('/labels/list', methods=['GET'])
    @wrap_response
    def labels_list(self):
        # If user is admin, query all distinct labels for models
        # 如果用户是管理员，则查询所有模型的不同标签
        if g.user.is_admin:
            result = (
                db.session.query(sqlalchemy.distinct(Labels.name))
                .filter(Labels.rel_type == 'model')
                .all()
            )
            return [i[0] for i in result]

        # Otherwise, query distinct labels for models created by the current user
        # 否则，查询当前用户创建的模型的不同标签
        result = (
            db.session.query(sqlalchemy.distinct(Labels.name))
            .outerjoin(Training_Model, Labels.rel_id == Training_Model.id)
            .filter(Labels.rel_type == 'model')
            .filter(Training_Model.created_by_fk == g.user.id)
            .all()
        )

        return [i[0] for i in result]

    # Get model detail API endpoint
    # 获取模型详细信息API端点
    @expose('/detail/<model_id>', methods=['GET'])
    @wrap_response
    def detail(self, model_id):
        # Query Training_Model by ID
        # 按ID查询Training_Model
        query = db.session.query(Training_Model).filter(Training_Model.id == model_id)

        # Apply user-specific filters
        # 应用用户特定过滤器
        if g.user.is_admin:
            pass
        elif g.user.is_tenant_admin:
            query = query.outerjoin(MyUser, MyUser.id == Training_Model.created_by_fk).filter(
                MyUser.tenant_id == g.user.tenant_id
            )
        else:
            query = query.filter(Training_Model.created_by_fk == g.user.id)

        model = query.first()
        if model is None:
            raise BizError(1000, '模型不存在')

        # Get associated labels
        # 获取关联标签
        labels = (
            db.session.query(Labels.name)
            .filter(Labels.rel_type == 'model', Labels.rel_id == Training_Model.id)
            .all()
        )
        # Get count of model versions
        # 获取模型版本数量
        cnt = db.session.query(Model_Version).filter_by(model_id=model_id).count()

        # Return model details
        # 返回模型详细信息
        return {
            'id': model.id,
            'name': model.name,
            'describe': model.describe,
            'versions': cnt,
            'type': EnumModelType.get_name(model.type),
            'scene': EnumModelScene.get_name(model.scene),
            'dimension': EnumModelDimension.get_name(model.dimension),
            'source': EnumModelSource.get_name(model.source),
            'labels': [i[0] for i in labels],
            'framework': model.framework,
            'sub_dimension': model.sub_dimension,
            'readme': model.readme,
            'is_public': model.is_public,
            'created_on': model.created_on.strftime('%Y-%m-%d %H:%M:%S'),
            'changed_on': model.changed_on.strftime('%Y-%m-%d %H:%M:%S'),
            'train_config': json.loads(model.train_config),
        }


# Get APIs by task type
# 根据任务类型获取API
def get_apis_by_type(task_type):
    # Get configurations for the task type
    # 获取任务类型的配置
    cfgs = AUTO_LEARNING_CFGS.get(task_type)
    if cfgs is None:
        logging.error(f'找不到task_type={task_type}的配置')
        return None

    apis = cfgs.get('apis')
    if apis is None or apis == '':
        logging.error(f'找不到task_type={task_type}的配置对应的apis')
        return None

    return apis


# Get ModelArts ID by task type, resource type, and Lora status
# 根据任务类型、资源类型和Lora状态获取ModelArts ID
def get_ma_id_by_type(task_type, resource_type=None, lora=None):
    # If Alluxio storage is used, return empty string
    # 如果使用Alluxio存储，则返回空字符串
    if is_alluxio_storage():
        return ''

    # Get configurations for the task type
    # 获取任务类型的配置
    cfgs = AUTO_LEARNING_CFGS.get(task_type)
    if cfgs is None:
        logging.error(f'找不到task_type={task_type}的配置')
        raise BizError(CommonErrorCode.INTERNAL_ERROR)
    ma_id = cfgs.get('ma_id')
    # If resource type is specified, get ma_id from resource type configurations
    # 如果指定了资源类型，则从资源类型配置中获取ma_id
    if resource_type:
        ma_id = cfgs.get(resource_type).get('ma_id')
        # If Lora is enabled, get ma_id from Lora configurations
        # 如果启用了Lora，则从Lora配置中获取ma_id
        if lora:
            ma_id = cfgs.get(resource_type, {}).get('lora', {}).get('ma_id')
    if ma_id is None or ma_id == '':
        logging.error(f'找不到task_type={task_type}的配置对应的ma_id')
        raise BizError(CommonErrorCode.INTERNAL_ERROR)

    return ma_id


# Get image name by task type, resource type, and Lora status
# 根据任务类型、资源类型和Lora状态获取镜像名称
def get_image_by_type(task_type, resource_type=None, lora=None):
    # Get configurations for the task type
    # 获取任务类型的配置
    cfgs = AUTO_LEARNING_CFGS.get(task_type)
    if cfgs is None:
        logging.error(f'找不到task_type={task_type}的配置')
        return ''

    image_name = cfgs.get('image_name')
    # If resource type is specified, get image_name from resource type configurations
    # 如果指定了资源类型，则从资源类型配置中获取image_name
    if resource_type:
        image_name = cfgs.get(resource_type, {}).get('image_name')
        # If Lora is enabled, get image_name from Lora configurations
        # 如果启用了Lora，则从Lora配置中获取image_name
        if lora:
            image_name = cfgs.get(resource_type, {}).get('lora', {}).get('image_name')
    if image_name is None or image_name == '':
        logging.error(f'找不到image_name配置,task_type={task_type}')
        return ''

    return image_name


# Get license ID by task type, resource type, and Lora status
# 根据任务类型、资源类型和Lora状态获取许可证ID
def get_license_id_by_type(task_type, resource_type=None, lora=None):
    # Get configurations for the task type
    # 获取任务类型的配置
    cfgs = AUTO_LEARNING_CFGS.get(task_type)
    if cfgs is None:
        logging.error(f'找不到task_type={task_type}的配置')
        return ''

    license_id = cfgs.get('license_id')
    # If resource type is specified, get license_id from resource type configurations
    # 如果指定了资源类型，则从资源类型配置中获取license_id
    if resource_type:
        license_id = cfgs.get(resource_type, {}).get('license_id')
        # If Lora is enabled, get license_id from Lora configurations
        # 如果启用了Lora，则从Lora配置中获取license_id
        if lora:
            license_id = cfgs.get(resource_type, {}).get('lora', {}).get('license_id')

    if license_id is None or license_id == '':
        logging.error(f'找不到license_id配置,task_type={task_type}')
        return ''

    return license_id


# Increment version string
# 递增版本字符串
def increment_version(version):
    # If version contains '.', split and increment parts
    # 如果版本包含'.'，则拆分并递增部分
    if '.' in version:
        parts = version.split('.')
        for i in range(len(parts) - 1, -1, -1):
            part = int(parts[i])
            part += 1
            if part == 10 and i > 0:
                parts[i] = '0'
                continue
            parts[i] = str(part)
            break
        return '.'.join(parts)
    else:
        # If no '.', return '0.0.2'
        # 如果没有'.'，则返回'0.0.2'
        return '0.0.2'


# Add the Training_Model_ModelView_Api to the appbuilder
# 将Training_Model_ModelView_Api添加到appbuilder
appbuilder.add_api(Training_Model_ModelView_Api)